package fr.chrysalis.ui.components.core
{
	import com.greensock.TweenMax;
	import com.greensock.easing.Linear;
	
	import flash.display.MovieClip;
	import flash.events.EventDispatcher;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	import fr.chrysalis.ui.UI;
	import fr.chrysalis.ui.UIConstants;
	import fr.chrysalis.ui.assets.IAsset;
	import fr.chrysalis.ui.components.events.ComponentEvent;
	import fr.chrysalis.ui.styles.Align;
	import fr.chrysalis.ui.utils.Animation;
	import fr.chrysalis.utils.DLinkedList;
	import fr.chrysalis.utils.DLinkedListNode;
	import fr.chrysalis.utils.Debug;

	public class Component extends EventDispatcher
	{
				
		/**
		 * Container du composant 
		 */
		protected var container:MovieClip;
		
		/**
		 * Réference du composant 
		 */
		protected var element:MovieClip;
		
		/**
		 * Réference au MC de surcharge de dimension 
		 */
		protected var size:MovieClip;
		
		/**
		 * Library d'animations
		 */
		protected var animations:Dictionary;
		
		/**
		 * Animation courante du composant
		 */
		protected var currentAnimation:Animation;
		
		/**
		 * Statut actuel d'activation du bouton 
		 */
		public var isActive:Boolean;
		
		/**
		 * Parent du composant
		 */
		public var parent:Component = null;
		
		/**
		 * Enfants du composant
		 */
		public var childs:DLinkedList = null;
		
		/**
		 * Couleur du débugage
		 */
		private var DEBUG_COLOR:uint;
		
		/**
		 * Id du component
		 */
		protected var id:int;
		
		/**
		 * Placement en x du composant 
		 */
		public function get x():int
		{
			return this.container.x;
		}
		/**
		 * @private
		 */
		public function set x(value:int):void
		{
			this.container.x = value;
			this.update();
		}

		/**
		 * Placement en y du composant 
		 */
		public function get y():int
		{
			return this.container.y;
		}
		/**
		 * @private
		 */
		public function set y(value:int):void
		{
			this.container.y = value;
			this.update();
		}
		
		/**
		 * Largeur du composant 
		 */
		public function get width():int
		{
			return this.bounds.width;
		}
		
		/**
		 * Hauteur du composant 
		 */
		public function get height():int
		{
			return this.bounds.height;
		}
		
		/**
		 * Bordures maximales du composant 
		 */
		protected var bounds:Rectangle;
		
		private var _selfAlign:String;
		/**
		 * Alignement actuel du composant par rapport à son point d'origine (voir constante de classe Align)
		 */
		public function get selfAlign():String
		{
			return _selfAlign;
		}
		/**
		 * @private
		 */
		public function set selfAlign(value:String):void
		{
			_selfAlign = value;
			switch(value){
				case Align.TOP_LEFT:
					this.element.x = -this.bounds.x;
					this.element.y = -this.bounds.y;
				break;
				case Align.TOP_CENTER:
					this.element.x = -this.bounds.x - this.bounds.width / 2;
					this.element.y = -this.bounds.y;
				break;
				case Align.TOP_RIGHT:
					this.element.x = -this.bounds.x - this.bounds.width;
					this.element.y = -this.bounds.y;
				break;
				
				case Align.MIDDLE_LEFT:
					this.element.x = -this.bounds.x;
					this.element.y = -this.bounds.y  - this.bounds.height / 2;
				break;
				case Align.MIDDLE_CENTER:
					this.element.x = -this.bounds.x - this.bounds.width / 2;
					this.element.y = -this.bounds.y  - this.bounds.height / 2;
				break;
				case Align.MIDDLE_RIGHT:
					this.element.x = -this.bounds.x - this.bounds.width;
					this.element.y = -this.bounds.y  - this.bounds.height / 2;
				break;
				
				case Align.BOTTOM_LEFT:
					this.element.x = -this.bounds.x;
					this.element.y = -this.bounds.y  - this.bounds.height;
				break;
				case Align.BOTTOM_CENTER:
					this.element.x = -this.bounds.x - this.bounds.width / 2;
					this.element.y = -this.bounds.y  - this.bounds.height;
				break;
				case Align.BOTTOM_RIGHT:
					this.element.x = -this.bounds.x - this.bounds.width;
					this.element.y = -this.bounds.y  - this.bounds.height;
				break;
			}
		}

		
		/**
		 * Initialise un composant
		 * @param asset
		 */
		public function Component(asset:IAsset)
		{
			this.id = UI.getInstance().getIdComponent();
			
			this.childs = new DLinkedList();
			
			var component:MovieClip = asset.get();
			
			try{
				component.x = component.y = 0;
			}catch(E:Error){
				component = new MovieClip();
			}
			
			this.size = new MovieClip();
			this.size.graphics.clear();
			this.size.graphics.beginFill( 0x000000, 0 );
			this.size.graphics.drawRect( 0, 0, 1, 1 );
			this.size.graphics.endFill();
			
			this.element = component;
			this.element.addChild(size);
			this.animations = Animation.analize(this.element);
			
			if(UI.debug){
				DEBUG_COLOR	= Math.random() * 16777216;
			}

			this.container = new MovieClip();
			this.container.addChild(this.element);
			this.updateBounds();
			this.selfAlign = Align.TOP_LEFT;
			this.update();
		}
		
		/**
		 * Met à jour les délimitations 
		 */
		public function updateBounds():void {
			this.bounds = this.element.getBounds(this.element);
		}
		
		/**
		 * Active le Composant
		 * Généralement override
		 */
		public function enable():void {
			this.isActive = true;
			this.dispatchEvent(new ComponentEvent(ComponentEvent.ENABLE));
		}
		
		/**
		 * Désactive le Composant
		 * Généralement override
		 */
		public function disable():void {
			this.isActive = false;
			this.dispatchEvent(new ComponentEvent(ComponentEvent.DISABLE));
		}
		
		/**
		 * Joue une animation du composant 
		 * @param animationName Nom de l'animation à jouer
		 * @param useCurrentFrame Utiliser la frame courante comme début d'animation
		 * @param reverse Lit on l'animation a l'envers
		 * @param loop Nombre de lecture de l'animation (-1 pour infite);
		 */
		public function play(animationName:String, useCurrentFrame:Boolean = false, reverse:Boolean = false, loop:int = 0):void {
			if(this.animations[animationName] != undefined){
				var animation:Animation = this.animations[animationName];
				this.currentAnimation = animation;
				
				var begin:int;
				var end:int;
				var start:int;
				
				if(reverse){
					end = animation.begin;
					start = animation.end;
				}else{
					end = animation.end;
					start = animation.begin;
				}
				
				if(useCurrentFrame){
					begin = this.element.currentFrame;
				}else{
					this.element.gotoAndStop(animation.name);
					begin = animation.begin;
				}
				
				var time:Number = Math.abs(end - begin) / 30;
				
				TweenMax.to(this.element, time, {ease: Linear.easeNone, frame:end, useFrames: false, repeat: loop, onRepeat: onRepeatTween, onRepeatParams:[this.element, begin, end, start] });
				function onRepeatTween(element:MovieClip, begin:int, end:int, start:int):void {
					
					element.gotoAndStop(start);
					
				}
			}
		}
		
		/**
		 * Affichage d'une bordure
		 */
		protected function showBackGround():void {
			this.element.graphics.clear();
			this.element.graphics.beginFill( DEBUG_COLOR, 0.4 );
			this.element.graphics.drawRect( this.bounds.x, this.bounds.y, this.width, this.height );
			this.element.graphics.endFill();
		}
		
		/**
		 * Suppression d'une bordure
		 */
		protected function deleteBackGround():void {
			this.element.graphics.clear();
		}
		
		/**
		 * Met à jour les valeurs manuellement
		 */
		public function update():void {					
			
			this.dispatchEvent(new ComponentEvent(ComponentEvent.UPDATE));
			
			this.updateBounds();
			
			if(UI.debug){
				showBackGround();	
			}
			
			if(this.parent){
				this.parent.update();
			}
			
		}
		
		/**
		 * Ajout d'un enfant 
		 * @param component Composant à ajouter
		 * @return 
		 */
		public function addChild(component:Component):void {
			this.childs.push(component);
			this.element.addChild(component.get());
			component.parent = this;
			this.update();
		}
		
		/**
		 * Supprime un enfant 
		 * @param component Composant à supprimer
		 * @return 
		 */
		public function removeChild(component:Component):void {
			this.childs.remove(component);
			this.element.removeChild(component.get());
			this.update();
		}
		
		/**
		 * Récupération du MovieClip Correspondant à la taille du composent défini 
		 * @return Le MC du size
		 */
		public function getSize():MovieClip {
			return this.size;
		}
		
		/**
		 * Récupération du MovieClip container 
		 * @return Le MC du component
		 */
		public function get():MovieClip {
			return this.container;
		}
		
		/**
		 * Supprime le composant ainsi que les enfants
		 */
		public function dealloc():void {
			var child:DLinkedListNode = this.childs.head;
			while (child)
			{
				(child.value as Component).dealloc();
			}
			
			this.size.parent.removeChild(this.size);
			this.element.parent.removeChild(this.element);
			this.container.parent.removeChild(this.container);
			
			this.parent = null;
			this.container = null;
			this.element = null;
			this.size = null;
			this.childs = null;
		}
		
	}
}